package org.dftproject.genesis.ui.pages.summary.details;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.draw2d.IFigure;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.widgets.Control;

public class FigureListViewer extends Viewer {

	private final IFigure container;
	private Object input;

	private IStructuredContentProvider contentProvider;
	private IFigureProvider figureProvider;
	private ViewerComparator comparator;

	private final List<Object> listMap = new ArrayList<Object>();
	private final Map<Object, IFigure> elementMap = new HashMap<Object, IFigure>();

	public FigureListViewer(IFigure container) {
		this.container = container;
	}

	public IFigure getContainer() {
		return container;
	}

	public IStructuredContentProvider getContentProvider() {
		return contentProvider;
	}

	public void setContentProvider(IStructuredContentProvider contentProvider) {
		IContentProvider oldContentProvider = this.contentProvider;
		this.contentProvider = contentProvider;
		if (oldContentProvider != null) {
			oldContentProvider.inputChanged(this, input, null);
			oldContentProvider.dispose();
			contentProvider.inputChanged(this, null, input);
			refresh();
		}
	}

	public IFigureProvider getFigureProvider() {
		return figureProvider;
	}

	public void setFigureProvider(IFigureProvider figureProvider) {
		this.figureProvider = figureProvider;
	}

	public ViewerComparator getComparator() {
		return comparator;
	}

	public void setComparator(ViewerComparator comparator) {
		this.comparator = comparator;
	}

	@Override
	public Object getInput() {
		return input;
	}

	@Override
	public void setInput(Object input) {
		Object oldInput = getInput();
		contentProvider.inputChanged(this, oldInput, input);
		this.input = input;
		inputChanged(input, oldInput);
	}

	public void add(Object element) {
		add(new Object[] { element });
	}

	public void add(Object[] elements) {
		for (Object element : elements) {
			if (elementMap.containsKey(element))
				continue;
			int index = indexForElement(element);
			insertItem(figureProvider, element, index);
		}
	}

	public void insert(Object element, int position) {
		if (getComparator() != null) {
			add(element);
			return;
		}

		insertItem(figureProvider, element, position);
	}

	public int getElementIndex(Object element) {
		return listMap.indexOf(element);
	}

	public Object getElementAt(int index) {
		if (index >= 0 && index < listMap.size())
			return listMap.get(index);
		return null;
	}

	@Override
	public void refresh() {
		listMap.clear();
		unmapAllElements();

		listRemoveAll();

		Object[] children = getSortedChildren();
		for (Object element : children) {
			IFigure figure = getFigure(figureProvider, element);
			container.add(figure);
			listMap.add(element);
			mapElement(element, figure);
		}
	}

	public void remove(Object element) {
		remove(new Object[] { element });
	}

	public void remove(Object[] elements) {
		for (Object element : elements) {
			int index = getElementIndex(element);
			if (index >= 0) {
				listMap.remove(index);
				container.remove(unmapElement(element));
			}
		}
	}

	@Override
	protected void inputChanged(Object newInput, Object oldInput) {
		refresh();
	}
	
	protected Object[] getRawChildren() {
		Object[] result = contentProvider.getElements(input);
		return result == null ? new Object[0] : result;
	}

	protected Object[] getSortedChildren() {
		Object[] result = getRawChildren();
		if (comparator != null) {
			result = result.clone();
			comparator.sort(this, result);
		}
		return result;
	}

	protected void insertItem(IFigureProvider figureProvider, Object element, int index) {
		IFigure figure = getFigure(figureProvider, element);
		if (figure != null) {
			container.add(figure, index);
			listMap.add(index, element);
			mapElement(element, figure);
		}
	}

	protected IFigure getFigure(IFigureProvider figureProvider, Object element) {
		if (figureProvider != null)
			return figureProvider.getFigure(element);
		return null;
	}

	protected int indexForElement(Object element) {
		if (comparator == null) {
			return listGetItemCount();
		}
		int count = listGetItemCount();
		int min = 0, max = count - 1;
		while (min <= max) {
			int mid = (min + max) / 2;
			Object data = listMap.get(mid);
			int compare = comparator.compare(this, data, element);
			if (compare == 0) {
				// find first item > element
				while (compare == 0) {
					++mid;
					if (mid >= count) {
						break;
					}
					data = listMap.get(mid);
					compare = comparator.compare(this, data, element);
				}
				return mid;
			}
			if (compare < 0) {
				min = mid + 1;
			} else {
				max = mid - 1;
			}
		}
		return min;
	}

	protected int listGetItemCount() {
		return listMap.size();
	}

	@SuppressWarnings("unchecked")
	protected void listRemoveAll() {
		List<IFigure> children = container.getChildren();
		for (IFigure child : children)
			container.remove(child);
	}

	protected void mapElement(Object element, IFigure figure) {
		elementMap.put(element, figure);
	}

	protected void unmapAllElements() {
		elementMap.clear();
	}

	protected IFigure unmapElement(Object element) {
		return elementMap.remove(element);
	}

	@Override
	public Control getControl() {
		throw new UnsupportedOperationException();
	}

	@Override
	public ISelection getSelection() {
		throw new UnsupportedOperationException();
	}

	@Override
	public void setSelection(ISelection selection, boolean reveal) {
		throw new UnsupportedOperationException();
	}

}
